/*
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version. You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.aitools.programd;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import org.aitools.programd.bot.Bot;
import org.aitools.programd.bot.Bots;
import org.aitools.programd.graph.Graphmaster;
import org.aitools.programd.graph.Nodemapper;
import org.aitools.programd.interfaces.ConsoleStreamAppender;
import org.aitools.programd.interpreter.Interpreter;
import org.aitools.programd.multiplexor.Multiplexor;
import org.aitools.programd.multiplexor.PredicateMaster;
import org.aitools.programd.parser.AIMLReader;
import org.aitools.programd.parser.BotsConfigurationFileParser;
import org.aitools.programd.plugin.Plugin;
import org.aitools.programd.plugin.Plugin.PluginStatus;
import org.aitools.programd.processor.ProcessorException;
import org.aitools.programd.processor.aiml.AIMLProcessorRegistry;
import org.aitools.programd.processor.botconfiguration.BotConfigurationElementProcessorRegistry;
import org.aitools.programd.server.servlet.ServicesFilter;
import org.aitools.programd.util.AIMLWatcher;
import org.aitools.programd.util.ClassUtils;
import org.aitools.programd.util.DeveloperError;
import org.aitools.programd.util.FileManager;
import org.aitools.programd.util.GlobalProperties;
import org.aitools.programd.util.Heart;
import org.aitools.programd.util.JDKLogHandler;
import org.aitools.programd.util.ManagedProcesses;
import org.aitools.programd.util.PluginSupportParam;
import org.aitools.programd.util.PluginsSupport;
import org.aitools.programd.util.URLTools;
import org.aitools.programd.util.UnspecifiedParameterError;
import org.aitools.programd.util.UserError;
import org.aitools.programd.util.UserSystem;
import org.aitools.programd.util.XMLKit;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

/**
 * The "core" of Program D, independent of any interfaces.
 * 
 * @author <a href="mailto:noel@aitools.org">Noel Bush</a>
 */
public class Core
{
	// Public access informational constants.

	/** Copyright notice. */
	public static final String[] COPYLEFT = { "Program D",
		"This program is free software; you can redistribute it and/or",
		"modify it under the terms of the GNU General Public License",
		"as published by the Free Software Foundation; either version 2",
	"of the License, or (at your option) any later version." };

	/** Version of this package. */
	public static final String VERSION = "4.6";
	
	/** Build identifier. */
	public static final String BUILD = "";

	/** The location of the AIML schema. */
	private static final String AIML_SCHEMA_LOCATION = "resources/schema/AIML.xsd";

	/** The namespace URI of the bot configuration. */
	public static final String BOT_CONFIG_SCHEMA_URI = "http://aitools.org/programd/4.6/bot-configuration";

	/** The namespace URI of the plugin configuration. */
	public static final String PLUGIN_CONFIG_SCHEMA_URI = "http://aitools.org/programd/4.6/plugins";

	/** The location of the plugins schema. */
	private static final String PLUGINS_SCHEMA_LOCATION = "resources/schema/plugins.xsd";

	/** The base URL. */
	private URL baseURL;

	/** The Settings. */
	protected CoreSettings settings;

	/** The Graphmaster. */
	private Graphmaster graphmaster;  

	/** The Multiplexor. */
	private Multiplexor multiplexor;

	/** The PredicateMaster. */
	private PredicateMaster predicateMaster;

	/** The bots. */
	private Bots bots;

	/** The processes. */
	private ManagedProcesses processes;

	/** The bot configuration element processor registry. */
	private BotConfigurationElementProcessorRegistry botConfigurationElementProcessorRegistry;

	/** The SAXParser used in loading AIML. */
	private SAXParser parser;

	/** The AIML processor registry. */
	private AIMLProcessorRegistry aimlProcessorRegistry;

	/** An AIMLWatcher. */
	private AIMLWatcher aimlWatcher;

	/** An interpreter. */
	private Interpreter interpreter;

	/** The logger for the Core. */
	private Logger logger = LogManager.getLogger("programd");

	/** Load time marker. */
	private boolean loadtime;

	/** Name of the local host. */
	private String hostname;

	/** A heart. */
	private Heart heart;

	/** The plugin config. */
	private Document pluginConfig;

	/** The status of the Core. */
	protected Status status = Status.NOT_STARTED;

	/** Possible values for status. */
	public static enum Status
	{
		/** The Core has not yet started. */
		NOT_STARTED,

		/** The Core has been properly intialized (internal, by constructor). */
		INITIALIZED,

		/** The Core has been properly set up (external, by user). */
		READY,

		/** The Core has shut down. */
		SHUT_DOWN,

		/** The Core has crashed. */
		CRASHED
	}

	// Convenience constants.
	private static final String EMPTY_STRING = "";

	/** The <code>*</code> wildcard. */
	public static final String ASTERISK = "*";

	private Map<Class<?>, Object> pluginSupportMap;

	// PLUGIN MANAGEMENT @author Giuseppe Miceli
	// Map of the plugins: pluginName\plugin
	private Map<String, Plugin> plugins = new LinkedHashMap<String, Plugin>();
	// Set of active plugins
	private Set<String> activePlugins = new HashSet<String>();
	/** The default plugin. */
	public Plugin defaultPlugin = new Plugin(GlobalProperties.defaultPluginName, PluginStatus.ON);
	// The current plugin: this indicates whose plugin aiml is being loaded.
	private Plugin currentPlugin = defaultPlugin;
	///////////////////////////////// end ////////////////////////////////////////

	//XXX
	public final static String pluginXmlLocation = GlobalProperties.pluginXmlLocation;
	public final static String pluginAIMLPluginLocation = "resources/schema/AIML-plugin.xsd";
	public final static String botsXMLLocation = "conf/bots.xml";    

	/**
	 * Initializes a new Core object with default property values
	 * and the given base URL.
	 * 
	 * @param base the base URL to use
	 */
	public Core(URL base)
	{
		this.settings = new CoreSettings();
		this.baseURL = base;

		// @author Giuseppe Miceli
		defaultPlugin.setPriority(0);
		this.activePlugins.add(defaultPlugin.getPluginName());
		
		FileManager.setRootPath(FileManager.getWorkingDirectory());
		
		start();
	}

	/**
	 * Initializes a new Core object with the properties from the given file
	 * and the given base URL.
	 * 
	 * @param base the base URL to use
	 * @param propertiesPath
	 */
	public Core(URL base, URL propertiesPath)
	{
		this.baseURL = base;
		this.settings = new CoreSettings(propertiesPath);
		

		// @author Giuseppe Miceli
		defaultPlugin.setPriority(0);
		this.activePlugins.add(defaultPlugin.getPluginName());
		
		FileManager.setRootPath(URLTools.getParent(this.baseURL));
		start();
	}

	/**
	 * Initializes a new Core object with the given CoreSettings object
	 * and the given base URL.
	 * 
	 * @param base the base URL to use
	 * @param settingsToUse the settings to use
	 */
	public Core(URL base, CoreSettings settingsToUse)
	{
		this.settings = settingsToUse;
		this.baseURL = base;
		
		// @author Giuseppe Miceli
		defaultPlugin.setPriority(0);
		this.activePlugins.add(defaultPlugin.getPluginName());
		
		FileManager.setRootPath(URLTools.getParent(this.baseURL));
		start();
	}

	public void restart(){
		FileManager.setRootPath(URLTools.getParent(this.baseURL));
		//this.shutdown();
		this.start();
	}

	/**
	 * Initializes and starts up the Core.
	 */
	public void start()
	{
		Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler());
		

		try {
			System.out.println("Loading plugins...");
			loadPlugin();
			System.out.println("Plugins loaded.");
		} catch (SAXException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		} catch (ParserConfigurationException e1) {
			e1.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

		// Use the stdout and stderr appenders in a special way, if they are defined.
		ConsoleStreamAppender stdOutAppender = ((ConsoleStreamAppender)Logger.getLogger("programd").getAppender("stdout"));
		if (stdOutAppender != null)
		{
			if (!stdOutAppender.isWriterSet())
			{
				stdOutAppender.setWriter(new OutputStreamWriter(System.out));
			}
		}

		ConsoleStreamAppender stdErrAppender = ((ConsoleStreamAppender)Logger.getLogger("programd").getAppender("stderr"));
		if (stdErrAppender != null)
		{
			if (!stdErrAppender.isWriterSet())
			{
				stdErrAppender.setWriter(new OutputStreamWriter(System.err));
			}
		}

		// Set up an interception of calls to the JDK logging system and re-route to log4j.
		JDKLogHandler.setupInterception();

		this.aimlProcessorRegistry = new AIMLProcessorRegistry();
		this.botConfigurationElementProcessorRegistry = new BotConfigurationElementProcessorRegistry();

		System.out.println("WorkingDirectory: "+FileManager.getWorkingDirectory());
		System.out.println("AIML_SCHEMA_LOCATION: "+AIML_SCHEMA_LOCATION);

		this.parser = XMLKit.getSAXParser(URLTools.contextualize(FileManager.getWorkingDirectory(), AIML_SCHEMA_LOCATION), "AIML");

		this.graphmaster = new Graphmaster(this);
		this.bots = new Bots();
		this.processes = new ManagedProcesses(this);

		// Get an instance of the settings-specified Multiplexor.
		this.multiplexor = ClassUtils.getSubclassInstance(Multiplexor.class, this.settings.getMultiplexorImplementation(),
				"Multiplexor", this);

		// Initialize the PredicateMaster and attach it to the Multiplexor.
		this.predicateMaster = new PredicateMaster(this);
		this.multiplexor.attach(this.predicateMaster);

		// Get the hostname (used occasionally).
		try
		{
			this.hostname = InetAddress.getLocalHost().getHostName();
		}
		catch (UnknownHostException e)
		{
			this.hostname = "unknown-host";
		}

		// Load the plugin config.
		try
		{
			this.pluginConfig = XMLKit.getDocumentBuilder(URLTools.contextualize(FileManager.getWorkingDirectory(), PLUGINS_SCHEMA_LOCATION),
					"plugin configuration").parse(
							URLTools.contextualize(this.baseURL, this.settings.getConfLocationPlugins())
							.toString());
		}
		catch (IOException e)
		{
			this.logger.error("IO error trying to read plugin configuration.", e);
		}
		catch (SAXException e)
		{
			this.logger.error("Error trying to parse plugin configuration.", e);
		}

		this.logger.info("Starting Program D version " + VERSION + BUILD + '.');
		this.logger.info(UserSystem.jvmDescription());
		this.logger.info(UserSystem.osDescription());
		this.logger.info(UserSystem.memoryReport());
		this.logger.info("Predicates with no values defined will return: \""
				+ this.settings.getPredicateEmptyDefault() + "\".");

		try
		{
			this.logger.info("Initializing "
					+ this.multiplexor.getClass().getSimpleName() + ".");

			// Initialize the Multiplexor.
			this.multiplexor.initialize();

			// Create the AIMLWatcher if configured to do so.
			if (this.settings.useWatcher())
			{
				this.aimlWatcher = new AIMLWatcher(this);
			}

			// Setup a JavaScript interpreter if supposed to.
			setupInterpreter();

			// Start the AIMLWatcher if configured to do so.
			startWatcher();

			this.logger.info("Starting up the Graphmaster.");

			// Start loading bots.
			loadBots(URLTools.contextualize(this.baseURL, this.settings.getStartupFilePath()));
			
			// --- @author Giuseppe Miceli
			for(String pluginName : plugins.keySet()){
				this.graphmaster.getRoot().addPlugin(pluginName);
			} 
			this.setCurrentPlugin(defaultPlugin);
			// --- end

			// Request garbage collection.
			System.gc();

			this.logger.info(UserSystem.memoryReport());

			// Start the heart, if enabled.
			startHeart();
		}
		catch (DeveloperError e)
		{
			alert("developer error", e);
			//return;
		}
		catch (UserError e)
		{
			alert("user error", e);
			//return;
		}
		catch (RuntimeException e)
		{
			alert("unforeseen runtime exception", e);
			//return;
		}
		catch (Throwable e)
		{
			alert("unforeseen problem", e);
			//return;
		}

		// Set the status indicator.
		this.status = Status.READY;

		// Exit immediately if configured to do so (for timing purposes).
		if (this.settings.exitImmediatelyOnStartup())
		{
			shutdown();
		}
	}

	private void startWatcher()
	{
		if (this.settings.useWatcher())
		{
			this.aimlWatcher.start();
			this.logger.info("The AIML Watcher is active.");
		}
		else
		{
			this.logger.info("The AIML Watcher is not active.");
		}
	}

	private void startHeart()
	{
		if (this.settings.heartEnabled())
		{
			this.heart = new Heart(this.settings.getHeartPulserate());
			// Add a simple IAmAlive Pulse (this should be more
			// configurable).
			this.heart.addPulse(new org.aitools.programd.util.IAmAlivePulse());
			this.heart.start();
			this.logger.info("Heart started.");
		}
	}

	private void setupInterpreter() throws UserError, DeveloperError
	{
		if (this.settings.javascriptAllowed())
		{
			if (this.settings.getJavascriptInterpreterClassname() == null)
			{
				throw new UserError(new UnspecifiedParameterError(
						"javascript-interpreter.classname"));
			}

			String javascriptInterpreterClassname = this.settings
			.getJavascriptInterpreterClassname();

			if (javascriptInterpreterClassname.equals(EMPTY_STRING))
			{
				throw new UserError(new UnspecifiedParameterError(
						"javascript-interpreter.classname"));
			}

			this.logger.info("Initializing " + javascriptInterpreterClassname + ".");

			try
			{
				this.interpreter = (Interpreter) Class.forName(javascriptInterpreterClassname)
				.newInstance();
			}
			catch (Exception e)
			{
				throw new DeveloperError(
						"Error while creating new instance of JavaScript interpreter.", e);
			}
		}
		else
		{
			this.logger.info("JavaScript interpreter not started.");
		}
	}

	/**
	 * Loads the <code>Graphmaster</code> with the contents of a given path.
	 * 
	 * @param path path to the file(s) to load
	 * @param botid
	 */
	public void load(URL path, String botid)
	{
		// Handle paths with wildcards that need to be expanded.
		if (true)
		{
			String spec = path.getFile();
			if (spec.indexOf(ASTERISK) != -1)
			{
				List<File> files = null;

				try
				{
					files = FileManager.glob(spec);
				}
				catch (FileNotFoundException e)
				{
					this.logger.warn(e.getMessage());
				}
				if (files != null)
				{
					for (File file : files)
					{
						load(URLTools.contextualize(URLTools.getParent(path), file.getAbsolutePath()), botid);
					}
				}
				return;
			}
		}

		Bot bot = this.bots.getBot(botid);

		if (!shouldLoad(path, bot))
		{
			return;
		}

		//FileManager.pushWorkingDirectory(URLTools.getParent(path));

		// Let the Graphmaster use a shortcut if possible.
		if (this.graphmaster.hasAlreadyLoaded(path))
		{
			if (this.graphmaster.hasAlreadyLoadedForBot(path, botid))
			{
				this.graphmaster.unload(path, bot);
				doLoad(path, botid);
			}
			if (this.logger.isDebugEnabled())
			{
				this.logger.debug(String.format("Graphaster has already loaded \"%s\" for some other bot.", path));
			}
			this.graphmaster.addForBot(path, botid);
		}
		else
		{
			if (this.settings.loadNotifyEachFile())
			{
				this.logger.info("Loading " + URLTools.unescape(path) + "....");
			}
			doLoad(path, botid);
			// Add it to the AIMLWatcher, if active.
			if (this.settings.useWatcher())
			{
				this.aimlWatcher.addWatchFile(path);
			}
		}
		//FileManager.popWorkingDirectory();
	}

	/**
	 * Reloads a file&mdash;it is not necessary to specify a particular
	 * botid here, because a reload of a file for one botid suffices
	 * for all bots associated with that file.
	 * 
	 * @param path
	 * @throws IllegalArgumentException if the given path is not actually loaded for <em>any</em> bot
	 */
	public void reload(URL path)
	{
		Set<String> botids = this.graphmaster.getURLCatalog().get(path);
		if (botids == null || botids.size() == 0)
		{
			throw new IllegalArgumentException("Called Core.reload() with a path that is not loaded by any bot.");
		}
		// Get any botid -- we don't care.
		doLoad(path, botids.iterator().next());
	}

	/**
	 * An internal method used by {@link #load(URL, String)}.
	 * @param path
	 * @param botid
	 */
	private void doLoad(URL path, String botid)
	{
		try
		{
			AIMLReader reader = new AIMLReader(this.graphmaster, path, botid, this.bots
					.getBot(botid), this.settings.getAimlSchemaNamespaceUri().toString());
			try
			{
				this.parser.getXMLReader().setProperty("http://xml.org/sax/properties/lexical-handler", reader);
			}
			catch (SAXNotRecognizedException e)
			{
				this.logger.warn("The XML reader in use does not support lexical handlers -- CDATA will not be handled.", e);
			}
			catch (SAXNotSupportedException e)
			{
				this.logger.warn("The XML reader in use cannot enable the lexical handler feature -- CDATA will not be handled.", e);
			}
			catch (SAXException e)
			{
				this.logger.warn("An exception occurred when trying to enable the lexical handler feature on the XML reader -- CDATA will not be handled.", e);
			}
			this.parser.parse(path.toString(), reader);
			//System.gc();
			this.graphmaster.addURL(path, botid);
		}
		catch (IOException e)
		{
			this.logger.warn(String.format("Error reading \"%s\": %s", URLTools.unescape(path), e.getMessage()), e);
		}
		catch (SAXException e)
		{
			this.logger.warn(String.format("Error parsing \"%s\": %s", URLTools.unescape(path), e.getMessage()), e);
		}
	}

	/**
	 * Tracks/checks whether a given path should be loaded, depending on whether
	 * or not it's currently &quot;loadtime&quot;; if the file has already been
	 * loaded and is allowed to be reloaded, unloads the file first.
	 * 
	 * @param path the path to check
	 * @param bot the bot for whom to check
	 * @return whether or not the given path should be loaded
	 */
	private boolean shouldLoad(URL path, Bot bot)
	{
		if (bot == null)
		{
			throw new NullPointerException("Null bot passed to loadCheck().");
		}

		Map<URL, Set<Nodemapper>> loadedFiles = bot.getLoadedFilesMap();

		if (loadedFiles.keySet().contains(path))
		{
			// At load time, don't load an already-loaded file.
			if (this.loadtime)
			{
				return false;
			}
			// At other times, unload the file before loading it again.
			this.graphmaster.unload(path, bot);
		}
		else
		{
			loadedFiles.put(path, new HashSet<Nodemapper>());
		}
		return true;
	}

	/**
	 * Sets "loadtime" mode
	 * (so accidentally duplicated paths in a load config
	 * won't be loaded multiple times).
	 */
	public void setLoadtime()
	{
		this.loadtime = true;
	}

	/**
	 * Unsets "loadtime" mode.
	 */
	public void unsetLoadtime()
	{
		this.loadtime = false;
	}

	/**
	 * Processes the given input using default values for userid (the hostname),
	 * botid (the first available bot), and no responder. The result is not
	 * returned. This method is mostly useful for a simple test of the Core.
	 * 
	 * @param input the input to send
	 */
	public synchronized void processResponse(String input)
	{
		if (this.status == Status.READY)
		{
			Bot bot = this.bots.getABot();
			if (bot != null)
			{
				this.multiplexor.getResponse(input, this.hostname, bot.getID());
				return;
			}
			this.logger.warn("No bot available to process response!");
			return;
		}
		//throw new DeveloperError("Check that the Core is ready before sending it messages.", new CoreNotReadyException());
	}

	/**
	 * Returns the response to an input, using a default TextResponder.
	 * 
	 * @param input the &quot;non-internal&quot; (possibly multi-sentence,
	 *            non-substituted) input
	 * @param userid the userid for whom the response will be generated
	 * @param botid the botid from which to get the response
	 * @return the response
	 */
	public synchronized String getResponse(String input, String userid, String botid)
	{
		if (this.status == Status.READY)
		{
			return this.multiplexor.getResponse(input, userid, botid);
		}
		// otherwise...
		//throw new DeveloperError("Check that the Core is running before sending it messages.", new CoreNotReadyException());
		return null;
	}

	/**
	 * Performs all necessary shutdown tasks. Shuts down the Graphmaster and all
	 * ManagedProcesses.
	 */
	public void shutdown()
	{
		this.logger.info("Program D is shutting down.");
		this.processes.shutdownAll();
		this.predicateMaster.saveAll();
		this.logger.info("Shutdown complete.");
		this.status = Status.SHUT_DOWN;
	}

	/**
	 * Notes the given Throwable and advises that the Core
	 * may no longer be stable.
	 * 
	 * @param e the Throwable to log
	 */
	public void alert(Throwable e)
	{
		alert(e.getClass().getSimpleName(), Thread.currentThread(), e);
	}

	/**
	 * Notes the given Throwable and advises that the Core
	 * may no longer be stable.
	 * 
	 * @param t the thread in which the Throwable was thrown
	 * @param e the Throwable to log
	 */
	public void alert(Thread t, Throwable e)
	{
		alert(e.getClass().getSimpleName(), t, e);
	}

	/**
	 * Notes the given Throwable and advises that the Core
	 * may no longer be stable.
	 * 
	 * @param description the description of the Throwable
	 * @param e the Throwable to log
	 */
	public void alert(String description, Throwable e)
	{
		alert(description, Thread.currentThread(), e);
	}

	/**
	 * Notes the given Throwable and advises that the Core
	 * may no longer be stable.
	 * 
	 * @param description the description of the Throwable
	 * @param t the thread in which the Throwable was thrown
	 * @param e the Throwable to log
	 */
	public void alert(String description, Thread t, Throwable e)
	{
		String throwableDescription = e.getClass().getSimpleName() + " in thread \"" + t.getName()
		+ "\"";
		if (e.getMessage() != null)
		{
			throwableDescription += ": " + e.getMessage();
		}
		else
		{
			throwableDescription += ".";
		}
		this.logger.error("Core may no longer be stable due to " + description + ":\n"
				+ throwableDescription);

		if (this.settings.onUncaughtExceptionsPrintStackTrace())
		{
			if (e instanceof UserError || e instanceof DeveloperError)
			{
				e.getCause().printStackTrace(System.err);
			}
			else
			{
				e.printStackTrace(System.err);
			}
		}
		//shutdown();
	}

	class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler
	{
		/**
		 * Causes the Core to fail, with information about the exception.
		 * 
		 * @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread,
		 *      java.lang.Throwable)
		 */
		public void uncaughtException(Thread t, Throwable e)
		{
			System.err.println("Uncaught exception " + e.getClass().getSimpleName()
					+ " in thread \"" + t.getName() + "\".");
			if (Core.this.settings.onUncaughtExceptionsPrintStackTrace())
			{
				e.printStackTrace(System.err);
			}
			Core.this.status = Core.Status.CRASHED;
			System.err.println("Core has crashed.  Shutdown may not have completed properly.");
		}
	}

	/**
	 * Loads bots from the indicated config file path.
	 * 
	 * @param path the config file path
	 */
	public void loadBots(URL path)
	{
		System.out.println(path);
		if (this.settings.useWatcher())
		{
			this.logger.debug("Suspending AIMLWatcher.");
			this.aimlWatcher.stop();
		}
		if (path.getProtocol().equals(FileManager.FILE))
		{
			FileManager.pushWorkingDirectory(URLTools.getParent(path));
		}
		try
		{
			BotsConfigurationFileParser bcfp = new BotsConfigurationFileParser(this);
			bcfp.process(path);
		}
		catch (ProcessorException e)
		{
			this.logger.error("Processor exception during startup: " + e.getExplanatoryMessage(), e);
		}
		if (path.getProtocol().equals(FileManager.FILE))
		{
			FileManager.popWorkingDirectory();
		}
		if (this.settings.useWatcher())
		{
			this.logger.debug("Restarting AIMLWatcher.");
			this.aimlWatcher.start();
		}
	}

	/**
	 * Loads a bot from the given path.  Will only
	 * work right if the file at the path actually
	 * has a &gt;bot&lt; element as its root.
	 * 
	 * @param path the bot config file
	 * @return the id of the bot loaded
	 */
	public String loadBot(URL path)
	{
		this.logger.info("Loading bot from \"" + path + "\".");
		/*if (path.getProtocol().equals(FileManager.FILE))
        {
            FileManager.pushWorkingDirectory(URLTools.getParent(path));
        }*/

		String id = null;

		try
		{
			id = new BotsConfigurationFileParser(this).processResponse(path);
		}
		catch (ProcessorException e)
		{
			this.logger.error(e.getExplanatoryMessage());
		}
		this.logger.info(String.format("Bot \"%s\" has been loaded.", id));
		/*if (path.getProtocol().equals(FileManager.FILE))
        {
            FileManager.popWorkingDirectory();
        }*/

		return id;
	}

	/**
	 * Unloads a bot with the given id.
	 * 
	 * @param id the bot to unload
	 */
	public void unloadBot(String id)
	{
		if (!this.bots.include(id))
		{
			this.logger.warn("Bot \"" + id + "\" is not loaded; cannot unload.");
			return;
		}
		Bot bot = this.bots.getBot(id);
		for (URL path : bot.getLoadedFilesMap().keySet())
		{
			this.graphmaster.unload(path, bot);
		}
		this.bots.removeBot(id);
		this.logger.info("Bot \"" + id + "\" has been unloaded.");
	}

	/*
	 * All of these "get" methods throw a NullPointerException if the item has
	 * not yet been initialized, to avoid accidents.
	 */

	/**
	 * @return the object that manages information about all bots
	 */
	public Bots getBots()
	{
		if (this.bots != null)
		{
			return this.bots;
		}
		throw new NullPointerException("The Core's Bots object has not yet been initialized!");
	}

	/**
	 * @param id the id of the bot desired
	 * @return the requested bot
	 */
	public Bot getBot(String id)
	{
		return this.bots.getBot(id);
	}

	/**
	 * @return the Graphmaster
	 */
	public Graphmaster getGraphmaster()
	{
		if (this.graphmaster != null)
		{
			return this.graphmaster;
		}
		throw new NullPointerException(
				"The Core's Graphmaster object has not yet been initialized!");
	}

	/**
	 * @return the Multiplexor
	 */
	public Multiplexor getMultiplexor()
	{
		if (this.multiplexor != null)
		{
			return this.multiplexor;
		}
		throw new NullPointerException(
				"The Core's Multiplexor object has not yet been initialized!");
	}

	/**
	 * @return the PredicateMaster
	 */
	public PredicateMaster getPredicateMaster()
	{
		if (this.predicateMaster != null)
		{
			return this.predicateMaster;
		}
		throw new NullPointerException(
				"The Core's PredicateMaster object has not yet been initialized!");
	}

	/**
	 * @return the BotConfigurationElementProcessorRegistry
	 */
	public BotConfigurationElementProcessorRegistry getBotConfigurationElementProcessorRegistry()
	{
		return this.botConfigurationElementProcessorRegistry;
	}

	/**
	 * @return the AIML processor registry.
	 */
	public AIMLProcessorRegistry getAIMLProcessorRegistry()
	{
		return this.aimlProcessorRegistry;
	}

	/**
	 * @return the AIMLWatcher
	 */
	public AIMLWatcher getAIMLWatcher()
	{
		if (this.aimlWatcher != null)
		{
			return this.aimlWatcher;
		}
		throw new NullPointerException(
				"The Core's AIMLWatcher object has not yet been initialized!");
	}

	/**
	 * @return the settings for this core
	 */
	public CoreSettings getSettings()
	{
		if (this.settings != null)
		{
			return this.settings;
		}
		throw new NullPointerException(
				"The Core's CoreSettings object has not yet been initialized!");
	}

	/**
	 * @return the active JavaScript interpreter
	 */
	public Interpreter getInterpreter()
	{
		if (this.interpreter != null)
		{
			return this.interpreter;
		}
		throw new NullPointerException(
				"The Core's Interpreter object has not yet been initialized!");
	}

	/**
	 * @return the local hostname
	 */
	public String getHostname()
	{
		return this.hostname;
	}

	/**
	 * @return the managed processes
	 */
	public ManagedProcesses getManagedProcesses()
	{
		return this.processes;
	}

	/**
	 * @return the status of the Core
	 */
	public Status getStatus()
	{
		return this.status;
	}

	/**
	 * @return the plugin config
	 */
	public Document getPluginConfig()
	{
		return this.pluginConfig;
	}

	/**
	 * @return the base URL
	 */
	public URL getBaseURL()
	{
		return this.baseURL;
	}

	/**
	 * @return the logger
	 */
	public Logger getLogger()
	{
		return this.logger;
	}

	//XXX questa funzione mi serve per caricare i vari plugin (ossia inserire i processor e riempire la mappa
	//pluginSupportMap
	public void loadPlugin() throws SAXException, IOException, ParserConfigurationException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException{    	

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();

		File f = new File(FileManager.getAbsolutePath(pluginXmlLocation));
		Document doc = builder.parse(f);

		NodeList list = doc.getChildNodes();

	/*	Map<String, File> pluginDirectories = new HashMap<String, File>();
		Map<String, Class<?>> pluginProcessor = new HashMap<String, Class<?>>();
		Map<String, Class<?>> pluginSupport = new HashMap<String, Class<?>>();
		Map<String, List<PluginSupportParam>> pluginSupportParams = new HashMap<String, List<PluginSupportParam>>();
		Map<String, String> pluginContainer = new HashMap<String, String>();
		Map<String, String> pluginElementProcessor = new HashMap<String, String>();
		Map<String, Class<?>> pluginServices = new HashMap<String, Class<?>>();*/

		// Plugins parsing.
		if(list.getLength()==1){
			Node pluginsNode = list.item(0);					

			list = pluginsNode.getChildNodes();

			for(int i=0; i<list.getLength(); i++){
				if(list.item(i).getNodeType() != Node.TEXT_NODE){
					Node pluginNode = list.item(i);

					// Parsing name and directory tags from the plugin node.
					NodeList properties = pluginNode.getChildNodes();
					String name = null;
					String directory = null;
					for(int j=0; j<properties.getLength(); j++){
						if(properties.item(j).getNodeType() != Node.TEXT_NODE){
							if(properties.item(j).getNodeName().equalsIgnoreCase("name")){
								name = properties.item(j).getTextContent();
							}else if(properties.item(j).getNodeName().equalsIgnoreCase("directory")){
								directory = properties.item(j).getTextContent();
							}
						}
					}
					
					// Plugin must contains these two properties.
					if(name!=null && directory!=null){
						// Checking directory existence.
						File dir = new File(FileManager.getAbsolutePath(directory));
						if(dir.exists()){
							Plugin plugin = new Plugin(name, PluginStatus.ON);
							plugin.setDirectory(dir);
							if (this.addPlugin(plugin)){
								
								// Adding plugin path to the list of plugins path.
								PluginsSupport.addPluginPath(name, dir.getAbsolutePath());
								File prop = new File(dir.getAbsolutePath()+File.separator+"plugin.properties");
								if(prop.exists()){
									PluginsSupport.addPluginPropertiesPath(name, prop.getAbsolutePath());
								}
							}
						}
					}
				}
			}
		}		
		System.out.println("Loading plugin libraries...");
		this.loadPluginLibraries();
		System.out.println("Plugin libraries loaded.");

		for(Plugin cPlugin:this.plugins.values()){
			// For every plugin
			
			if(cPlugin.getDirectory().exists()){
				
				String pluginXmlConfig = cPlugin.getDirectory().getAbsolutePath()+File.separator+"plugin.xml";
				System.out.println("Reading: "+pluginXmlConfig);
				
				File pluginXmlConfigFile = new File(pluginXmlConfig);
				Document pluginDoc = builder.parse(pluginXmlConfigFile);
				NodeList pList = pluginDoc.getChildNodes();

				if(pList.getLength()==1){
					Node pluginsNode = pList.item(0);
					pList = pluginsNode.getChildNodes();					

					List<String> processor = new ArrayList<String>();
					List<String> support = new ArrayList<String>();
					Map<String, List<PluginSupportParam>> supportParams = new HashMap<String, List<PluginSupportParam>>();
					String container = null;
					String elementProcessor = null;
					Map<String, Class<?>> pluginServs = new HashMap<String, Class<?>>();
					
					// Parsing plugin xml
					for(int i=0; i<pList.getLength(); i++){
						if(pList.item(i).getNodeType()!=Node.TEXT_NODE){
							
							// processors parsing
							if(pList.item(i).getNodeName().equalsIgnoreCase("processors")){
								NodeList prs = pList.item(i).getChildNodes();
								for(int j=0; j<prs.getLength(); j++){
									if(prs.item(j).getNodeType()!=Node.TEXT_NODE){
										processor.add(prs.item(j).getAttributes().getNamedItem("class").getNodeValue());
									}
								}

							// supports parsing
							}else if(pList.item(i).getNodeName().equalsIgnoreCase("supports")){
								NodeList prs = pList.item(i).getChildNodes();
								
								for(int j=0; j<prs.getLength(); j++){
									Node sup = prs.item(j);
									
									if(sup.getNodeType()!=Node.TEXT_NODE){
										
										String supportClass = sup.getAttributes().getNamedItem("class").getNodeValue();
										support.add(supportClass);
										List<PluginSupportParam> params = new ArrayList<PluginSupportParam>();										
										NodeList pars = sup.getChildNodes();
										
										for(int k=0; k<pars.getLength(); k++){
											Node par = pars.item(k);
											if(par.getNodeType()!=Node.TEXT_NODE){
												
												if(par.getNodeName().equalsIgnoreCase("param")){
													String name = par.getAttributes().getNamedItem("name").getNodeValue();
													String paramClass = par.getAttributes().getNamedItem("class").getNodeValue();
													String value = par.getTextContent();
													
													if(name!=null && paramClass != null && value != null){
														
														PluginSupportParam param = new PluginSupportParam();
														param.setName(name);
														param.setValue(value);
														param.setParamClass(Class.forName(paramClass));
														params.add(param);
													}
												}
											}
										}
										supportParams.put(supportClass, params);
									}
								}
								
							// container parsing
							}else if(pList.item(i).getNodeName().equalsIgnoreCase("container")){
								container = pList.item(i).getTextContent().trim();
							
							// processorElement parsing
							}else if(pList.item(i).getNodeName().equalsIgnoreCase("processorElement")){
								elementProcessor = pList.item(i).getTextContent().trim();
							
							// services parsing
							}else if(pList.item(i).getNodeName().equalsIgnoreCase("services")){
								NodeList prs = pList.item(i).getChildNodes();
								for(int j=0; j<prs.getLength(); j++){
									Node serv = prs.item(j);
									if(serv.getNodeType()!=Node.TEXT_NODE){
										String serviceClassName = serv.getAttributes().getNamedItem("class").getNodeValue();
										String serviceName = serv.getAttributes().getNamedItem("name").getNodeValue();

										Class<?> serviceClass = Class.forName(serviceClassName);
										pluginServs.put(serviceName, serviceClass);										
									}									
								}
							}
						}						
					}

					if(processor.size()>0 || support.size()>0 || pluginServs.size()>0){

						for(String p:processor){
							Class<?> cProcessor = Class.forName(p);
							
							cPlugin.getProcessors().put(p, cProcessor);
							//pluginProcessor.put(p, cProcessor);
						}

						for(String s:support){
							Class<?> cSupport = Class.forName(s);
							
							cPlugin.getSupportMap().put(s, cSupport);
							cPlugin.getSupportParameters().put(s, supportParams.get(s));
							//pluginSupport.put(s, cSupport);
							//pluginSupportParams.put(s, supportParams.get(s));
						}

						
						if(container!=null){
							cPlugin.setContainer(container);
							//pluginContainer.put(pluginName, container);
						}
						else{
							cPlugin.setContainer("");
							//pluginContainer.put(pluginName, "");							
						}
						
						if(elementProcessor!=null){
							cPlugin.setElementProcessor(elementProcessor);
							//pluginElementProcessor.put(pluginName, elementProcessor);
						}
						else{
							cPlugin.setElementProcessor("");
							//pluginElementProcessor.put(pluginName, "");							
						}
						
						for(String name:pluginServs.keySet()){
							cPlugin.getServices().put(name, pluginServs.get(name));
							//pluginServices.put(name, pluginServs.get(name));
						}
					}					
				}				
			}
		}

		List<String> processorList = new ArrayList<String>();
		processorList.addAll(Arrays.asList(AIMLProcessorRegistry.getPROCESSOR_LIST()));
		for(Plugin plugin: plugins.values()){
		//for(String processor:pluginProcessor.keySet()){
			for (Class<?> processor:plugin.getProcessors().values()){
				processorList.add(processor.getCanonicalName());
			}				
		}
		AIMLProcessorRegistry.setPROCESSOR_LIST(processorList.toArray(new String[processorList.size()]));

		System.out.println("Writing Bots xml...");
		this.writeBotsXML();
		//this.writeBotsXML(pluginDirectories);
		System.out.println("Writing AIML Plugin XSD...");
		this.writeAIMLPluginXSD();
		//this.writeAIMLPluginXSD(pluginContainer, pluginElementProcessor);
		System.out.println("Loading plugin support map...");
		this.loadPluginSupportMap();
		//this.loadPluginSupportMap(pluginSupport, pluginSupportParams);
		System.out.println("Setting plugin services...");
		//this.loadPluginServices(pluginServices);
		this.loadPluginServices();
	}

	private void loadPluginLibraries() throws MalformedURLException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{

		Map<String, URL> urlsMap = new HashMap<String, URL>();
		for(Plugin plugin:this.plugins.values()){
			if (!plugin.getPluginName().equalsIgnoreCase(GlobalProperties.defaultPluginName)) {
				
				File dir = plugin.getDirectory();
				File libDir = new File(dir.getAbsoluteFile()+File.separator+"lib");
	
				File[] files = libDir.listFiles();
				if (files != null){
					for(File file:files){
						if(file.getName().endsWith(".jar")){
							//urlsMap.put(file.getName(), file.toURL());
							urlsMap.put(file.getName(), file.toURI().toURL());
						}
					}
				}
			}
		}	  	

		if(urlsMap.size()>0){
			Class<?>[] parameters = new Class[]{URL.class};

			URLClassLoader sysloader = (URLClassLoader) this.getClass().getClassLoader();

			List<URL> oldUrl = Arrays.asList(sysloader.getURLs());

			Class<?> sysclass = URLClassLoader.class;

			Method method = sysclass.getDeclaredMethod("addURL", parameters);
			method.setAccessible(true);

			for(String name:urlsMap.keySet()){
				URL url = urlsMap.get(name);

				if(!oldUrl.contains(url)){
					this.logger.info("Adding library "+url);
					Object[] array = new Object[]{url};

					method.invoke(sysloader, array);
				}
			}
		}
	}

	private void loadPluginSupportMap() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException{
		pluginSupportMap = new HashMap<Class<?>, Object>();

		//for(String cl:pluginSupport.keySet()){
		for(Plugin plugin:this.plugins.values()){
			for (String cl:plugin.getSupportMap().keySet()){
			
				Class<?> sClass = plugin.getSupportMap().get(cl);				
				List<PluginSupportParam> params = plugin.getSupportParameters().get(cl);
				
				if(params!=null&&params.size()>0){
					Class<?>[] classes = new Class<?>[params.size()];
					Object[] objects = new Object[params.size()];
	
					for(int i=0; i<params.size();i++){
						PluginSupportParam param = params.get(i);
						classes[i]=param.getParamClass();
	
						Constructor<?> constructor = param.getParamClass().getConstructor(Class.forName("java.lang.String"));
						objects[i] = constructor.newInstance(param.getValue());
					}
					Constructor<?> constructor = sClass.getConstructor(classes);
					pluginSupportMap.put(sClass, constructor.newInstance(objects));			
				}
				else{
					Constructor<?> constructor = sClass.getConstructor();
					pluginSupportMap.put(sClass, constructor.newInstance());
				}
	
			}
		}
	}

	private void writeAIMLPluginXSD() throws IOException{
		String containers = "";
		String elements = "";

		String[] flag = {
				"<!--container-->",
				"<!--end container-->",
				"<!--processor elements-->",
				"<!--end processor elements-->"
		};

		for (Plugin plugin:this.plugins.values()){
			if (plugin.getContainer() != null){
				containers += plugin.getContainer() + "\n";
			}
			if (plugin.getElementProcessor() != null){
				elements += plugin.getElementProcessor() + "\n";
			}
		}
		/*
		for(String plugin:pluginContainer.keySet()){
			containers+=pluginContainer.get(plugin)+"\n";
			elements+=pluginElementProcessor.get(plugin)+"\n";
		}*/

		String text = "";

		BufferedReader bufferedReader = new BufferedReader(new FileReader(FileManager.getAbsolutePath(pluginAIMLPluginLocation)));

		while(bufferedReader.ready()){
			text += bufferedReader.readLine()+"\n";
		}
		bufferedReader.close();   	

		int initContainer = text.indexOf(flag[0])+flag[0].length();
		int endContainer = text.indexOf(flag[1]);

		text = text.substring(0, initContainer)+"\n"+containers+text.substring(endContainer, text.length());

		int initElements = text.indexOf(flag[2])+flag[2].length();
		int endElements = text.indexOf(flag[3]);

		text = text.substring(0, initElements)+"\n"+elements+text.substring(endElements, text.length());

		FileWriter fileWriter = new FileWriter(FileManager.getAbsolutePath(pluginAIMLPluginLocation));

		fileWriter.write(text);
		fileWriter.flush();
		fileWriter.close();
	}

	/** 
	 * @author Mario Scriminaci (v1)
	 * @author Giuseppe Miceli (v2)
	 * */
	private void writeBotsXML() throws IOException{
		String bots = "";
		String[] flag = {
				"<!--plugins aiml-->",
				"<!--end plugins aiml-->"
		};

		String learnTag1="<learn";
		String learnTag2="</learn>";

		// plugins.xml gets modified 
		for(Plugin plugin:plugins.values()){
			File dir = plugin.getDirectory();
			File aimlDir= new File(dir.getAbsolutePath()+File.separator+"aiml");
			if(aimlDir.exists()){
				String path;
				if (aimlDir.getAbsolutePath().startsWith(File.separator)){
					path = aimlDir.getAbsolutePath()+File.separator+"*.aiml";
				} else {
					path = File.separator+aimlDir.getAbsolutePath()+File.separator+"*.aiml";					
				}
				bots+=learnTag1 + " plugin=\"" + plugin.getPluginName() + "\">" + path +learnTag2+"\n";
				//bots+=learnTag1 + " plugin=\"" + plugin.getPluginName() + "\">" + File.separator + aimlDir.getAbsolutePath() + File.separator + "*.aiml" +learnTag2+"\n";
			}  
		}

		String text = "";

		BufferedReader bufferedReader = new BufferedReader(new FileReader(FileManager.getAbsolutePath(botsXMLLocation)));

		while(bufferedReader.ready()){
			text += bufferedReader.readLine()+"\n";
		}
		bufferedReader.close();  

		int initPlugins = text.indexOf(flag[0])+flag[0].length();
		int endPlugins = text.indexOf(flag[1]);

		text = text.substring(0, initPlugins)+"\n"+bots+text.substring(endPlugins, text.length());

		FileWriter fileWriter = new FileWriter(FileManager.getAbsolutePath(botsXMLLocation));

		fileWriter.write(text);
		fileWriter.flush();
		fileWriter.close();
	}

	private void loadPluginServices(){
		Map<String, Class<?>> allServices = new HashMap<String, Class<?>>();
		for(Plugin plugin:this.plugins.values()){
			allServices.putAll(plugin.getServices());
		}
		ServicesFilter.setPluginsServices(allServices);    	
		ServicesFilter.setReloadFilter(true);
	}

	public void setPluginSupportMap(Map<Class<?>, Object> pluginSupportMap) {
		this.pluginSupportMap = pluginSupportMap;
	}

	public Map<Class<?>, Object> getPluginSupportMap() {
		return pluginSupportMap;
	}
	

	/**
	 * The method adds a <code>Plugin</code> with a specific <code>PluginStatus</code> (ON\OFF) 
	 * @author Giuseppe Miceli
	 * 
	 * @param
	 * @param 
	 * @return 
	 */
	public Boolean addPlugin(String pluginName, PluginStatus status){
		
		if (!plugins.containsKey(pluginName)){
			Plugin p = new Plugin(pluginName, status);
			p.setPriority(this.plugins.size());			
			plugins.put(pluginName, p);
			
			if (status == PluginStatus.ON){
				activatePlugin(pluginName);
			}
			
			return true;
		}
		return false;
	}
	
	/**
	 * The method adds a <code>Plugin</code> with <code>PluginStatus</code> set to ON.
	 * @author Giuseppe Miceli
	 * 
	 * @param
	 * @param 
	 * @return 
	 */
	public Boolean addPlugin(String pluginName){
		return addPlugin(pluginName, PluginStatus.ON);
	}
	
	/**
	 * The method adds a <code>Plugin</code> with <code>PluginStatus</code> set to ON.
	 * @author Giuseppe Miceli
	 * 
	 * @param
	 * @param 
	 * @return 
	 */
	public Boolean addPlugin(Plugin plugin){
		if (!this.plugins.containsKey(plugin.getPluginName())) {
			if (plugin.getPriority() == null){
				plugin.setPriority(this.plugins.size());
			}				
			Boolean boh = this.plugins.containsKey(plugin.getPluginName()); 
			this.plugins.put(plugin.getPluginName(), plugin);

			if (plugin.getStatus() == PluginStatus.ON){
				this.activatePlugin(plugin);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * The method activates a <code>Plugin</code>, turning its <code>PluginStatus</code> to ON.
	 * @author Giuseppe Miceli
	 * 
	 * @param 
	 * @return 
	 */
	private Boolean activatePlugin(Plugin plugin) {
		return activatePlugin(plugin.getPluginName());
	}

	/**
	 * The method activates a <code>Plugin</code>, turning its <code>PluginStatus</code> to ON.
	 * @author Giuseppe Miceli
	 * 
	 * @param 
	 * @return 
	 */
	public Boolean activatePlugin(String pluginName){
		// The plug-in must exist
		if (!plugins.containsKey(pluginName)) {
			return false;
		}
		
		plugins.get(pluginName).setStatus(PluginStatus.ON);
				
		if (!activePlugins.contains(pluginName)){
			activePlugins.add(pluginName);		
		} 		
		return true;
	}
	
	/**
	 * The method deactivates a <code>Plugin</code>, turning its <code>PluginStatus</code> to OFF.
	 * @author Giuseppe Miceli
	 * 
	 * @param
	 * @param 
	 * @return 
	 */
	public Boolean deactivatePlugin(String pluginName){
		// The plug-in must exist
		if (!plugins.containsKey(pluginName)) return false;
		
		plugins.get(pluginName).setStatus(PluginStatus.OFF);
				
		if (activePlugins.contains(pluginName)){
			activePlugins.remove(pluginName);		
		}
		return true;
	}

	public Boolean deactivatePlugin(Plugin plugin) {
		return this.deactivatePlugin(plugin.getPluginName());		
	}
	
	/**
	 * @return the currentPlugin
	 * @author Giuseppe Miceli
	 */
	public Plugin getCurrentPlugin() {
		return currentPlugin;
	}

	/**
	 * @param currentPlugin the currentPlugin to set
	 * @author Giuseppe Miceli
	 */
	public void setCurrentPlugin(Plugin plugin) {
		this.currentPlugin = plugin;
	}

	/**
	 * @return the plugins map
	 */
	public Map<String, Plugin> getPlugins() {
		return plugins;
	}

	/**
	 * @return the activePlugins names
	 */
	public Set<String> getActivePlugins() {
		return activePlugins;
	}	
}